package com.wang.lotterysystem.service.mq;

import cn.hutool.core.date.DateUtil;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.wang.lotterysystem.common.exception.ServiceException;
import com.wang.lotterysystem.common.utils.MailUtil;
import com.wang.lotterysystem.controller.enums.ActivityPrizeStatusEnum;
import com.wang.lotterysystem.controller.enums.ActivityPrizeTiersEnum;
import com.wang.lotterysystem.controller.enums.ActivityStatusEnum;
import com.wang.lotterysystem.controller.enums.ActivityUserStatusEnum;
import com.wang.lotterysystem.controller.param.DrawPrizeParam;
import com.wang.lotterysystem.dao.dataobject.activity.ActivityPrizeDO;
import com.wang.lotterysystem.dao.dataobject.WinningRecordDO;
import com.wang.lotterysystem.dao.mapper.ActivityPrizeMapper;
import com.wang.lotterysystem.dao.mapper.WinningRecordMapper;
import com.wang.lotterysystem.service.DrawPrizeService;
import com.wang.lotterysystem.service.dto.activity.ActivityStatusConvertDTO;
import com.wang.lotterysystem.service.manager.ActivityStatusManager;
import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.rabbit.annotation.RabbitHandler;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;

import static com.wang.lotterysystem.common.config.DirectRabbitConfig.QUEUE_NAME;

@Component
@Slf4j
@RabbitListener(queues = QUEUE_NAME) //监听的队列
public class MqReceiver {  //mq消费者

    @Autowired
    private ObjectMapper objectMapper;

    @Autowired
    private DrawPrizeService drawPrizeService;

    @Autowired
    private ActivityStatusManager activityStatusManager;

    @Autowired
    private ThreadPoolTaskExecutor asyncServiceExecutor; //注入对应名字的Bean

    @Autowired
    private MailUtil mailUtil;

    @Autowired
    private ActivityPrizeMapper activityPrizeMapper;

    @Autowired
    private WinningRecordMapper winningRecordMapper;


    /**
     * 处理消息
     * @param message 消息
     */
    @RabbitHandler
    public void process(Map<String,String> message) throws JsonProcessingException {

        log.info("接收到生产者消息:{}",message.toString());

        //从map中获取到中奖信息
        String messageData = message.get("messageData");

        //将中奖信息字符串反序列化为指定对象
        DrawPrizeParam drawPrizeParam = objectMapper.readValue(messageData, DrawPrizeParam.class);

        try {

            //-----1.对抽奖参数进行校验----

            /**
             * 这里之所以使用Boolean作为返回值,是为了用户能够重复发送抽奖信息.
             * 如果采用抛出异常的方式的话:在第二次发送之后,就会触发回滚操作,这时候又会把奖品和用户状态全部回滚回来,显然不合逻辑
             */

            if (!drawPrizeService.checkDrawPrizeValid(drawPrizeParam)){
                //如果抽奖参数有误 --> 直接返回
                return;
            }

            //-----2.根据抽奖参数进行状态扭转-----
            convertStatus(drawPrizeParam);

            //-----3.保存中奖记录-----
            List<WinningRecordDO> winningRecordDOList = drawPrizeService.saveWinningRecords(drawPrizeParam);

            //拿到中奖记录之后,发送邮件
            syncExecute(winningRecordDOList);
        } catch (ServiceException e) { //处理ServiceException

            log.error("处理mq消息异常:{},{},{}",e.getCode(),e.getCode(),e.getMessage());

            //出现异常之后-->需要把异常前进行的操作回滚掉
            rollback(drawPrizeParam);

            //回滚后抛出异常
            throw e;
        } catch (Exception e) { //处理Exception(兜底)

            log.error("处理mq消息异常:{}",e.getMessage());

            //出现异常之后-->需要把异常前进行的操作回滚掉
            rollback(drawPrizeParam);

            //回滚后抛出异常
            throw e;
        }
    }

    /**
     * 扭转活动相关信息的状态
     * @param drawPrizeParam 抽奖信息参数
     */
    private void convertStatus(DrawPrizeParam drawPrizeParam) throws JsonProcessingException {

        //创建一个状态扭转参数类,并对其进行赋值
        ActivityStatusConvertDTO statusConvertDTO = new ActivityStatusConvertDTO();

        statusConvertDTO.setActivityId(drawPrizeParam.getActivityId());
        statusConvertDTO.setPrizeId(drawPrizeParam.getPrizeId());
        statusConvertDTO.setActivityTargetStatus(ActivityStatusEnum.COMPLETED);
        statusConvertDTO.setPrizeTargetStatus(ActivityPrizeStatusEnum.COMPLETED);

        //中奖人员id列表winnerIdList
        List<Long> winnerIdList = new ArrayList<>();

        //遍历参数拿到中奖人员id列表 赋给winnerIdList
        for (DrawPrizeParam.Winner winner : drawPrizeParam.getWinnerList()) {
            winnerIdList.add(winner.getUserId());
        }

        statusConvertDTO.setUserIds(winnerIdList);
        statusConvertDTO.setUserTargetStatus(ActivityUserStatusEnum.COMPLETED);

        //扭转状态
        activityStatusManager.handlerEvent(statusConvertDTO);
    }

    /**
     * 抽奖之后进行发送邮件给中奖者
     * @param winningRecordDOList 获奖记录
     */
    public void syncExecute(List<WinningRecordDO> winningRecordDOList){

        asyncServiceExecutor.execute(() -> sendMail(winningRecordDOList));
    }


    /**
     * 给中奖者发送邮件
     * @param recordDOList 中奖记录
     */
    private void sendMail(List<WinningRecordDO> recordDOList) {
        if(CollectionUtils.isEmpty(recordDOList)) {
            log.warn("中奖名单为空！");
            return;
        }
        for (WinningRecordDO winningRecordDO : recordDOList) {
            // Hi,胡一博。恭喜你在抽奖活动活动中获得二等奖:吹风机。获奖奖时间为18:18:44,请尽快领取您的奖励
            String context = "Hi，" + winningRecordDO.getWinnerName() + "。恭喜你在"
                    + winningRecordDO.getActivityName() + "活动中获得"
                    + ActivityPrizeTiersEnum.checkForName(winningRecordDO.getPrizeTier()).getMessage()
                    + "：" + winningRecordDO.getPrizeName() + "。获奖时间为"
                    + DateUtil.formatTime(winningRecordDO.getWinningTime()) + "，请尽快领 取您的奖励！";
            mailUtil.sendSampleMail(winningRecordDO.getWinnerEmail(),
                    "中奖通知", context);
        }
    }

    /**
     * 回滚活动相关信息的状态
     * @param drawPrizeParam 抽奖参数
     */
    private void rollback(DrawPrizeParam drawPrizeParam) throws JsonProcessingException {

        if (!convertStatusSuccess(drawPrizeParam)){
            //状态没有扭转成功,直接返回
            return;
        }

        //构建状态扭转参数,回滚回原来的状态
        ActivityStatusConvertDTO statusConvertDTO = new ActivityStatusConvertDTO();

        statusConvertDTO.setActivityId(drawPrizeParam.getActivityId());
        statusConvertDTO.setPrizeId(drawPrizeParam.getPrizeId());

        for (DrawPrizeParam.Winner winner :drawPrizeParam.getWinnerList()) {
            //拿到中奖人员id列表
            List<Long> userIds = new ArrayList<>();
            userIds.add(winner.getUserId());
            //将其设置进参数中
            statusConvertDTO.setUserIds(userIds);
        }

        //回滚活动,奖品,人员状态
        statusConvertDTO.setActivityTargetStatus(ActivityStatusEnum.RUNNING);
        statusConvertDTO.setPrizeTargetStatus(ActivityPrizeStatusEnum.INIT);
        statusConvertDTO.setUserTargetStatus(ActivityUserStatusEnum.INIT);

        //回滚数据
        activityStatusManager.rollback(statusConvertDTO);

        //回滚之后判断中奖记录是否已经落库
        if (!hasRecords(drawPrizeParam)){
            //如果没有落库,直接返回
            return;
        }

        //如果落库-->删除当前记录的数据,包括缓存和数据库中的数据
        drawPrizeService.deleteRecords(drawPrizeParam.getActivityId(),drawPrizeParam.getPrizeId());
    }

    private boolean hasRecords(DrawPrizeParam param) {
        //从库中查询是否存在记录
        return winningRecordMapper.countByActivityIdAndPrizeId(param.getActivityId(),param.getPrizeId()) > 0;
    }

    /**
     * 判断活动奖品,人员,活动状态是否已经扭转成功
     * @param drawPrizeParam 抽奖参数
     * @return 返回是否扭转成功
     */
    private boolean convertStatusSuccess(DrawPrizeParam drawPrizeParam){
        //由于扭转活动,奖品,人员状态封装为了一个事务,所以我们只需要判断一个就相当于都判断了,
        // 但是我们不可以以活动状态为准,这是由于活动状态必须等人员和奖品都扭转之后才可以扭转
        ActivityPrizeDO activityPrizeDO =
                activityPrizeMapper.selectByActivityAndPrizeId(
                        drawPrizeParam.getActivityId(),drawPrizeParam.getPrizeId()
                );
        return activityPrizeDO.getStatus().equals(ActivityPrizeStatusEnum.COMPLETED.name());
    }
}